ION 是当前 Android 流行的内存分配管理机制,在多媒体部分中使用的最多,例如从 Camera 到 Display,从 Mediaserver 到 Surfaceflinger,都会利用 ION 进行内存分配管理。
ION 的前任是 PMEM,关于 PMEM 我在 M030/M04X 项目中有接触过,后来由于 PMEM 的一些局限性,Google 推出了 ION 来取代 PMEM,当前 ION 已经融合到 Linux 主线,被广泛使用。
对于魅族,从 M65 项目开始,相机的内存分配管理就已经利用 ION 进行了,本文会结合 M65、M76 和 M86 项目开发介绍下我对 ION 内存管理机制的理解和感悟。
ION 基本概念
ION,最显著的特点是它可以被用户空间的进程之间或者内核空间的模块之间进行内存共享,而且这种共享可以是零拷贝的。在实际使用中,ION 和 VIDEOBUF2、DMA-BUF、V4L2 等结合的很紧密。本文主要介绍 ION,其它子系统感兴趣的话后续会陆续进行介绍。
ION 是在各种 heaps 上分配内存,通过 ion_buffer 来描述所分配的内存。
下图展示了 ION 的基本框架。图中 PID1、PID2、PID3 表示用户空间进程。ION core 表示 ION 核心层,它提供设备创建、注册等服务,同时提供统一的接口给用户使用。ION Driver 利用 ION core 对相应功能进行实现,可以说它是具体平台相关的,例如 SAMSUNG 平台、QUALCOMM 平台和 MTK 平台都会依据自己的特性开发相应的 ION Driver。
上图虽然描述的是用户空间进程使用 ION 的情形,但实际上,在内核空间同样可以直接使用 ION 来分配、管理内存。例如 M76、M86 平台的相机驱动,都有直接使用 ION 分配和管理内存。
主要数据结构
数据结构是程序设计的基础,代码看的多了,其实可以从数据结构看出其能提供的基本功能和大致用法。
为了抓住纲领,本文将抓住 ION 的主要数据结构进行介绍。
ion_device
ion_device 是 ION 很重要很基础的数据结构,用 struct ion_device 结构体描述,一般在一个系统中只有一个本实例。例如在 M86 中,就是在 exynos_ion_v2.c 的 exynos_ion_probe() 函数中创建了系统唯一的本实例。
1 | struct ion_device { |
struct ion_device 其实是 ION 的核心结构,不过由于对于使用 ION 的用户而言是屏蔽的,即如果是单纯使用 ION,不需要直接和本结构打交道。但是想完全的理解 ION,需要对其有所了解。
struct ion_device 是由 ion_device_create() [ion.c] 分配、初始化。
- dev 成员,是 struct miscdevice 类型,所以可想而知 ION 是作为 MISC 设备注册进系统的。从这点还可以看出来,用户空间使用 ION 时必定需要使用 open 啊,ioctl 啊系统调用。事实也正是如此。
- heaps 成员,在 M86 使用过的 KERNEL LINUX 3.10 中是 struct plist_head 类型,但是在此之前并不是此类型,例如在 M65 使用过的 KERNEL LINUX 3.4 中是 struct rb_root 类型。可见随着 KERNEL 和 ION 的演进,struct ion_device 的实现会有所改变。本字段管理的是属于本 struct ion_device 的所有 struct ion_heap 实例。
- clients 成员,是 struct rb_root 类型,struct rb_root 是红黑树,属于二叉树的一种。本字段管理的是 struct ion_client 实例。
ion_client
struct ion_client 是由 ion_client_create() [ion.c] 创建,在创建时必须指定上文提到的 struct ion_device 实例。
1 | struct ion_client { |
- node 成员,是 struct rb_node 结构类型,用于将本 struct ion_client 实例加入到 struct ion_device,具体的是 struct ion_device::clients。
- device 成员,是 struct ion_device 指针结构类型,指向所属的 struct ion_device 实例。
- handles 成员,是 struct rb_root 结构类型,管理其所拥有的 handle,即 struct ion_handle 实例。一个 struct ion_handle 实例表示一个 buffer,即 struct ion_buffer 实例。而 struct ion_buffer 就是从 heap,即 struct ion_heap 中分配的内存。
ion_heap
struct ion_heap 表示 ION 中的重要概念 heap。系统会通过链表或者红黑树,这取决与你所使用的 KERNEL 版本,来管理所有的 heap,这些 heap 可用 struct ion_device::heaps 字段来寻找。
1 | struct ion_heap { |
- node 成员,是 struct plist_node 结构,用于将本 heap 实例加入到 struct ion_device 所管理的链表中,详情可以参见 ion_device_add_heap() [ion.c] 函数。
- dev 成员,是 struct ion_device 结构体指针类型,用于指示本 heap 挂在哪一个 struct ion_device 实例下了。
- type 成员,是 enum ion_heap_type 类型,用于表示本 heap 属于哪种类型。用户在使用 ION 分配内存时需要指定 heap 的种类。关于本字段,后续会结合使用方法进行更详细的介绍。
- ops 成员,是 struct ion_heap_ops 类型,它很重要!本字段提供的回调函数是用于从本 heap 中分配内存时所时用的。请参见 ion_buffer_create() [ion.c],它会调用 struct ion_heap_ops::allocate() 等回调函数。
1 | struct ion_heap_ops { |
- id 成员,也很重要,它可表示优先级,在分配内存时选择哪一个 heap 有关,必须唯一。
ion_handle
struct ion_handle 其实就是表示 buffer,用户空间常用它来表示 buffer。本结构通过 ion_handle_create() [ion.c] 分配、初始化。
1 | struct ion_handle { |
- ref 成员,是 struct kref 结构类型,它在内核中被广泛的用来表示引用计数。ion_handle 的创建和销毁都与其有关,下文会有介绍。
- client 成员,是 struct ion_client 指针类型,指向其所述的 ion_client 实例。
- buffer 成员,是 struct ion_buffer 指针类型,指向真正的 buffer 所在,它可以说是 stuct ion_handle 的核心成员了。
下面就来介绍 struct ion_buffer 结构。在分配内存时,也是先通过 ion_buffer_create() 创建 ion_buffer 实例,然后交给 ion_handle_create() 创建 ion_handle 实例。
ion_buffer
struct ion_buffer 很重要,通过 ION 分配的内存就是通过它表示的。它和上面提到的 ion_handle 的区别主要在于一个是用户空间使用的,一个是内核空间使用的。即虽然常用的接口函数中使用的是 struct ion_handle,但实际上真正表示内存的其实是 struct ion_buffer。
1 | struct ion_buffer { |
- ref 成员,是 struct kref 结构实例,维护了本 ion_buffer 的引用计数。当引用计数为 0 时会释放该 buffer,即 struct ion_heap_ops::free 会被调用。分配用 ION_IOC_ALLOC 型 ioctl 系统调用,相应的释放用 ION_IOC_FREE 型 ioctl 系统调用。
- size 成员,当然是本 buffer 所表示的空间的大小,用字节表示。
- priv_virt 成员,是所分配内存的虚拟地址啦,它常与 struct sg_table,或者封装它的结构,有关。它不是我们在内核中读写时所需的内核虚拟地址啦,内核虚拟地址使用 vaddr 成员来表示的。一般而言,物理内存不连续的,使用本字段;否则使用下面的 priv_phys 字段,如 struct ion_heap_ops contig_heap_ops。
- priv_phys 成员,表示所分配的内存的物理地址。它适用于分配的物理内存是连续的 ion heap。这种连续的物理内存:在将其映射到用户空间时,即获取用户空间虚拟地址,可以使用 remap_pfn_range() [memory.c] 这个方便的接口;在将其映射到内核空间时,即获取内核虚拟地址,可以使用 vmap() [vmalloc.c] 这个方便的接口。例子详见 struct ion_heap_ops contig_heap_ops [exynos_ion.c]。priv_virt 成员和 priv_phys 成员组成了一个联合体,其实都表示地址,只不过不同的场景下具体用的不一样而已。
- kmap_cnt 成员,记录本 buffer 被映射到内核空间的次数。
- vaddr 成员,是本 buffer 对应的内核虚拟地址。当 kmap_cnt 不为 0 时有效。可以通过 ion_map_kernel() [ion.c] 来获取本 buffer 对应的内核虚拟地址。ion_map_kernel() [ion.c] 实际上调用的是相应 struct ion_heap_ops::map_kernel 回调函数获取相应的虚拟地址的。
- dmap_cnt 成员,记录本 buffer 被 mapped for DMA 的次数。
- sg_table 成员,是 struct sg_table 结构体类型的指针。本字段与 DMA 操作有关,而且仅仅在 dmap_cnt 成员变量不为 0 时是有效的。可以通过 ion_buffer_create() [ion.c] 来初始化本成员变量,该函数实际上是调用相应 ion_heap 所属的 struct ion_heap_ops::map_dma 回调函数获取本字段的值的。
- dirty 成员,表示 bitmask。即以位图表示本 buffer 的哪一个 page 是 dirty 的,即不能直接用于 DMA。dirty 表示 DMA 的不一致性,即 CPU 缓存中的内容与内存中的实际内容不一样。
事实上,ION 涉及到的数据结构还有很多,这里列举的都是一些非常重要的。
下图展示了上文介绍到的数据结构的基本关系。
重要函数分析
函数对数据进行处理,完成特定的任务,体现算法的具体实现。
ion_device_create
前面分析 ION 的一些核心数据结构时曾经指出,ION 会注册进 MISC 设备,这样用户空间就可以像使用 MISC 设备一样使用 ION 进行内存分配了。
先来看 ION 是如何注册进 MISC 子系统的。
1 | struct ion_device *ion_device_create(long (*custom_ioctl) |
本函数最重要的是分配并初始化了核心 struct ion_device 实例,并将其和 MISC 设备结合起来,这样用户空间就可以通过 open()、ioctl() 等系统调用使用它了。
ion_open
用户空间要想使用 ION 进行内存分配,首先必须对设备节点 /dev/ion 进行 open() 系统调用。
1 | static int ion_open(struct inode *inode, struct file *file) |
ion_open() 函数最重要的作用就是创建了 struct ion_client 实例。这样,后续就可以利用 ioctl 系统调用从其中分配内存了。
ION 系统提供的 ioctl 类型有很多,常用的有 ION_IOC_ALLOC、ION_IOC_FREE、ION_IOC_SHARE 和 ION_IOC_IMPORT 等等。
1 |
下面就抽出几个典型的进行分析。
ion_alloc
这是当用户空间执行 ION_IOC_ALLOC 型 ioctl() 系统调用时所执行的。
1 | struct ion_handle *ion_alloc(struct ion_client *client, size_t len, |
ion_free
这是当用户空间执行 ION_IOC_FREE 型 ioctl() 系统调用时所执行的。
1 | void ion_free(struct ion_client *client, struct ion_handle *handle) |
此函数比较简单,重点就是通过 ion_handle_put() 来对上文提到的 struct ion_handle::ref 这个 reference count 减一,当 ref 减到 0 时,就会调用 ion_handle_destroy() 来销毁 ion_handle 实例。
从前文的分析可知,用户空间在利用 ION 分配内存时,需要指定具体的 heap mask,即告知 ION 想从哪种 heap 分配内存。
下面就来介绍下。
HEAP 种类
以下是通过 ION 分配内存时,可能会使用到的 heap mask。
1 |
以 M65 项目为例,系统定义了 4 种 heap,见 dev-ion.c。
ION_HEAP_TYPE_SYSTEM
本 heap 的名字为:”ion_noncontig_heap”。相应的 heap mask 为 1,即 ION_HEAP_SYSTEM_MASK。
在本 struct ion_heap 上分配内存的操作集是:
struct ion_heap_ops system_heap_ops [ion_system_heap.c]
其内存可以从 HIGHMEM 中分配,所以其物理内存可能不连续。
在调试 M65 CAMERA 的 HDR 功能时,一开始发现其写入速度很慢很慢,经过调查,后来在通过 ION 分配内存时指定 ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC | ION_FLAG_PRESERVE_KMAP 标志后速度得到明显提高。
ION_HEAP_TYPE_SYSTEM_CONTIG
本 heap 的名字为:”ion_contig_heap”,相应的 heap mask 为 2,即 ION_HEAP_SYSTEM_CONTIG_MASK。
在本 struct ion_heap 上分配内存的操作集是:
struct ion_heap_ops kmalloc_ops [ion_system_heap.c]
其内存分配 allocate 回调函数为:ion_system_contig_heap_allocate() [ion_system_heap.c],该函数很简单,就是利用 kzalloc() 分配内存。
ION_HEAP_TYPE_EXYNOS
本 heap 的名字为:”exynos_noncontig_heap”,相应的 heap mask 为 32,即 ION_HEAP_EXYNOS_MASK。
在本 struct ion_heap 上分配内存的操作集是:
struct ion_heap_ops vmheap_ops [exynos_ion.c]
其内存可以从 HIGHMEM 中分配,所以其物理内存不一定连续。详情可以见其 allocate 回调函数 ion_exynos_heap_allocate() [exynos_ion.c]。
ION_HEAP_TYPE_EXYNOS_CONTIG
本 heap 的名字为:”exynos_contig_heap”。相应的 heap mask 为 16,即 ION_HEAP_EXYNOS_CONTIG_MASK。
在本 struct ion_heap 上分配内存的操作集是:
struct ion_heap_ops contig_heap_ops [exynos_ion.c]
内存由 CMA 分配,所以可以保证其物理地址是连续的。详情可以参见其 allocate 回调函数 ion_exynos_contig_heap_allocate() [exynos_ion.c]。
因为本函数是利用 CMA 分配内存,所以可以推测肯定有地方预留了 CMA 所需的物理内存。分析代码后可以发现,这个地方就位于 mach-m65.c 文件。
M65 的相机驱动使用了本 ION_HEAP_TYPE_EXYNOS_CONTIG 类型的 heap 来分配内存。
按道理,当用户空间获取了 struct ion_handle 实例后,就已经完成了 ION 内存的分配任务。但实际上,为了在不同的进程间,甚至在用户空间和内核空间共享这段内存使用,用户空间还通常需要调用 ION_IOC_SHARE 型ioctl(),获取 ion buffer 相关的 fd,这就和 dma_buf 子系统联系起来了。
1 | int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle) |
到这里,ION 使用过程中涉及到的重要函数都已经介绍完全。当然要完全理解这些函数的细节,需要用户对 dma_buffer 有一定的了解。对 dma_buffer 的介绍不属于本文的范围,有兴趣的话可以参见我写的其它相关文档。
结语
关于 Android ION 内存管理机制的介绍就到这里。本文先介绍了什么是 ION,为什么要用 ION。ION 是为了解决内存碎片管理而引入的通用内存管理器,用于取代 PMEM 机制。然后介绍了下 ION 中的重要数据结构,对 struct ion_device、struct ion_client、struct ion_heap、struct ion_handle 和 struct ion_buffer 进行了详细的介绍,并对它们之间的关系进行了阐述。接着,从使用 ION 的场景出发,介绍了一些重要的函数,例如 ion_alloc(),并以实际的相机开发为例介绍了系统中各个 ion_heap 的种类和各自的内存特性。本文还对进程之间、内核空间和用户空间之间的内存共享进行了介绍。
This is copyright.